package uy.org.softtech.cssuite.web.procesadores;

import static org.springframework.util.Assert.notNull;
import static org.springframework.util.StringUtils.hasText;
import static org.springframework.web.servlet.support.RequestContextUtils.getLocale;
import static uy.org.softtech.cssuite.helpers.HelperFechaHora.fechaActual;

import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStreamReader;
import java.net.URI;
import java.util.Date;
import java.util.Locale;

import javax.annotation.PostConstruct;
import javax.annotation.Resource;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import javax.servlet.http.HttpSession;

import org.apache.http.HttpResponse;
import org.apache.http.HttpStatus;
import org.apache.http.client.HttpClient;
import org.apache.http.client.methods.HttpGet;
import org.apache.http.client.utils.URIBuilder;
import org.apache.http.impl.client.DefaultHttpClient;
import org.apache.log4j.Logger;
import org.codehaus.jackson.map.ObjectMapper;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.context.MessageSource;
import org.springframework.security.core.Authentication;
import org.springframework.security.core.AuthenticationException;
import org.springframework.security.core.userdetails.UsernameNotFoundException;
import org.springframework.security.web.authentication.AuthenticationFailureHandler;
import org.springframework.security.web.authentication.AuthenticationSuccessHandler;
import org.springframework.stereotype.Component;

import uy.org.softtech.cssuite.core.Excepcion;
import uy.org.softtech.cssuite.entidad.usuario.Usuario;
import uy.org.softtech.cssuite.excepciones.AutenticacionException;
import uy.org.softtech.cssuite.excepciones.EntidadNoActualizadaException;
import uy.org.softtech.cssuite.excepciones.EntidadNoEncontradaException;
import uy.org.softtech.cssuite.repositorios.RepositorioUsuarios;
import uy.org.softtech.cssuite.web.entidad.DetalleUsuario;
import uy.org.softtech.cssuite.web.entidad.Geolocalizacion;

/**
 * Implementacion de {@link AuthenticationFailureHandler} y {@link AuthenticationSuccessHandler} para
 * procesamiento posterior en caso de autenticación exitosa o fallida.
 * 
 * @author fabian.lobo
 * @see AuthenticationFailureHandler#onAuthenticationFailure(HttpServletRequest, HttpServletResponse, AuthenticationException)
 * @see AuthenticationSuccessHandler#onAuthenticationSuccess(HttpServletRequest, HttpServletResponse, Authentication)
 */
@Component(value = "procesador-autenticacion")
public class ProcesadorAutenticacion implements AuthenticationFailureHandler, AuthenticationSuccessHandler {
	
	private final String SERVICIO = getClass().getName();

	private Logger log = Logger.getLogger(getClass());
	
	@Resource(name = "repositorio-usuarios")
	private RepositorioUsuarios repositorioUsuarios;
	
	@Resource(name = "fuente-mensajes")
	private MessageSource fuenteMensajes;
	
	@Value(value = "#{'${autenticacion.maximaCantidadIntentos}'}")
	private Long maximaCantidadIntentos = Long.valueOf(3); /* La cantidad máxima de intentos (3 por defecto). */
	
	@Value(value = "#{'${autenticacion.maximoTiempoEspera}'}")
	private Long maximoTiempoEspera = Long.valueOf(300); /* La cantidad máxima de espera (300 segundos por defectos). */
	
	/**
	 * Método para verificar que todos los parámetros han sido seteados correctamente.
	 * 
	 * @author fabian.lobo
	 */
	@PostConstruct
	public void initialize(){
		notNull(repositorioUsuarios(), "La propiedad [repositorioUsuarios] de [" + getClass().getName() + "] no ha sido establecida.");
		notNull(fuenteMensajes(), "La propiedad [fuenteMensajes] de [" + getClass().getName() + "] no ha sido establecida.");
		notNull(maximaCantidadIntentos(), "La propiedad [maximaCantidadIntentos] de [" + getClass().getName() + "] no ha sido establecida.");
		notNull(maximoTiempoEspera(), "La propiedad [maximoTiempoEspera] de [" + getClass().getName() + "] no ha sido establecida.");
		
		log.info("Implementación del post-procesador de autenticacion ha sido inicializado.");
	}

	@Override
	public void onAuthenticationSuccess(HttpServletRequest request, HttpServletResponse response, Authentication authentication) throws IOException, ServletException {
		
		Locale locale = getLocale(request);
		
		String username = request.getParameter("j_username");
		Usuario usuario = null;
		try {
			//Borramos la cantidad de intentos fallidos anteriores
			usuario = repositorioUsuarios().buscarUsuarioPorEmail(username);
			usuario.setIntentos(Usuario.ZERO);
			repositorioUsuarios().actualizar(usuario, SERVICIO + "#onAuthenticationSuccess");
			
			URI uri = URI.create(request.getRequestURL().toString());
			URIBuilder uriBuilder = new URIBuilder(uri);
			final String host = uriBuilder.getHost();
			final HttpSession sesion = request.getSession();
			
			//Actualizamos la información de la conexión
			if(authentication.getPrincipal() instanceof DetalleUsuario) {
				DetalleUsuario detalles = (DetalleUsuario) authentication.getPrincipal();
				detalles.setNavegador(request.getHeader("User-Agent"));
				detalles.setLenguaje(request.getHeader("Accept-Language"));
				detalles.setDireccion(host);
			}
			
			//Generamos la geolocalización en otro thread para liberar el request
			new Thread(new Runnable() {
				@Override
				public void run() {
					try {
						HttpClient clienteHttp = new DefaultHttpClient();
						HttpGet solicitud = new HttpGet("http://freegeoip.net/json/" + host);
						HttpResponse respuesta = clienteHttp.execute(solicitud);
						if (respuesta != null) {
							// Leyendo la respuesta
							StringBuilder builder = new StringBuilder();
							switch (respuesta.getStatusLine().getStatusCode()) {
								case HttpStatus.SC_ACCEPTED:
								case HttpStatus.SC_OK:
									String line = "";
									BufferedReader reader = new BufferedReader(new InputStreamReader(respuesta.getEntity().getContent()));
									while ((line = reader.readLine()) != null) {
										builder.append(line);
									}
									reader.close();
								break;
							}
							if (builder.length() > 0) {
								// Convertimos la respuesta del formato JSON a un objeto JAVA
								ObjectMapper mapper = new ObjectMapper();
								Geolocalizacion geolocalizacion = mapper.readValue(builder.toString(), Geolocalizacion.class);

								// Guardamos los datos en la sesion
								if(geolocalizacion != null) {
									sesion.setAttribute("GEOLOCALIZACION", geolocalizacion);
								}
							}
						}
					} catch (Throwable e) {
						log.error(e);
					}
				}
			}).start();
			
		} catch (EntidadNoActualizadaException e) {
			log.error(e);
		} catch (EntidadNoEncontradaException e) {
			log.error(e);
		} catch (Throwable e) {
			log.error(e);
		}
		
		response.sendRedirect(request.getContextPath() + "/inicio.do?lang=" + locale.getLanguage().toUpperCase());
	}

	@Override
	public void onAuthenticationFailure(HttpServletRequest request, HttpServletResponse response, AuthenticationException exception) throws IOException, ServletException {
		Locale locale = getLocale(request);
		String mensaje = "";
		
		String username = request.getParameter("j_username");
		if(!hasText(username)) {
			mensaje = fuenteMensajes().getMessage("mensaje.usuarioRequerido", null, "Por favor, ingrese su usuario (correo electrónico).", locale);
			exception = new AutenticacionException("usuario", mensaje, exception);
		} else {
			String password = request.getParameter("j_password");
			if(!hasText(password)) {
				mensaje = fuenteMensajes().getMessage("mensaje.passwordRequerida", null, "Por favor, ingrese su contraseña.", locale);
				exception = new AutenticacionException("password", mensaje, exception);
			} else {
				Usuario usuario = null;
				try {
					usuario = repositorioUsuarios().buscarUsuarioPorEmail(username);
				} catch (EntidadNoEncontradaException e) {
					mensaje = Excepcion.validaMensaje(e.getMessage());
					log.error(e);
					if((exception instanceof UsernameNotFoundException) == false) {
						exception = new AutenticacionException("usuario", mensaje);
					}
				}
				if(usuario != null && !usuario.isEliminado() && !usuario.isBloqueado()){
					int intentos = usuario.getIntentos();
					
					//Si ya sobrepasamos la ventana de espera, consideramos éste como el primer intento
					Date ahora = fechaActual();
					Date ultimoFallo = usuario.getUltimoIntentoFallido() != null ? usuario.getUltimoIntentoFallido() : ahora;
					if(ahora.getTime() - ultimoFallo.getTime() > maximoTiempoEspera() * 1000){
						intentos = 0;
					}
					intentos++;
					usuario.setIntentos(intentos);
					usuario.setUltimoIntentoFallido(ahora);
								
					if(usuario.getIntentos() == maximaCantidadIntentos().intValue()){
						//Si se llega a la cantidad máxima de intentos, bloqueamos la cuenta y cambiamos el mensaje
						usuario.setBloqueado(true);
						mensaje = fuenteMensajes().getMessage("mensaje.cuentaBloqueada", null, "Esta cuenta ha sido bloqueada. Por favor, contact al administrador del sistema para desbloquearla.", locale);
						exception = new AutenticacionException("usuario", mensaje, exception);
					}
					try {
						repositorioUsuarios().actualizar(usuario, SERVICIO + "#onAuthenticationFailure");
					} catch (EntidadNoActualizadaException e) {
						log.error(e);
					}
				} else if(usuario != null && usuario.isBloqueado()) {
					mensaje = fuenteMensajes().getMessage("mensaje.cuentaBloqueada", null, "Esta cuenta ha sido bloqueada. Por favor, contact al administrador del sistema para desbloquearla.", locale);
					exception = new AutenticacionException("usuario", mensaje, exception);
				}
			}
		}
		
		//Convertimos cualquier otra excepcion a una AutenticacionException
		if(exception instanceof AutenticacionException == false) {
			mensaje = exception.getMessage();
			exception = new AutenticacionException("password", mensaje, exception);
		}
		request.getSession().setAttribute("SPRING_SECURITY_LAST_EXCEPTION", exception);
		
		//Redirigimos a la página de inicio
		response.sendRedirect(request.getContextPath() + "/entrar.do?lang=" + locale.getLanguage().toUpperCase());
	}

	/*
	 *  GETTERS
	 */
	public RepositorioUsuarios repositorioUsuarios() {
		return repositorioUsuarios;
	}
	public MessageSource fuenteMensajes() {
		return fuenteMensajes;
	}
	public Long maximaCantidadIntentos() {
		return maximaCantidadIntentos;
	}
	public Long maximoTiempoEspera() {
		return maximoTiempoEspera;
	}
}
